package player;
import java.util.*;

import board.Board;
import board.DepthFirstSearch;
import board.Piece;
import list.ArrayList;

import test.Printer;

public class Minimax {

	 /*
 		ITS WORKING WOOOOOOO HOOOOO
	 */
	boolean firstMove;
	HashMap<Board, Integer> boardMap;
	public Minimax() {
		firstMove = true;
		boardMap = new HashMap<Board, Integer>(1000, .5f);
	}
	
	
	/**
	 * Uses alpha beta search to choose the best move for a player.
	 * @param b The board
	 * @param whose_turn  0 for black or 1 for white
	 * @return the best move for the machine player color based on the best score
	 */
	public static Move chooseMove(Board b, int color, int depth){
		Minimax m = new Minimax();
		return m.getBestMove(b, color, depth);
	}
	Move getBestMove(Board b, int color, int depth) {
		int bestScore = 1000; // a low number representing the opponent winning
		if (color == Piece.WHITE)
			bestScore = -1000;
		int score;
		Move bestMove = null;
		if (firstMove == true) { // choosing a first move for each machine player
			firstMove = false;
			bestMove = new Move(4, 4);
			if (!b.isLegal(bestMove, color))
				bestMove = new Move(3, 3);
			if (b.isLegal(bestMove, color))
				return bestMove;
		}
		for (int y = 0; y < b.board.length; y++) { //traverses board to find all possible moves in child nodes
			for (int x = 0; x <b.board[0].length; x++) {
				Move theMove = new Move(x, y);
				if (b.isLegal(theMove, color)) {
					b.makeMove(theMove, color); //make the temporary move
					if (color == Piece.BLACK)
						score = abBlack(b, (color+1)%2, depth-1, -1000, 1000); //find the max score for white and min score for black
					else
						score = abWhite(b, (color+1)%2, depth-1, -1000, 1000);
					b.clearSpot(x, y); //undoes the temporary move
					System.out.println(score + "curScore");
					System.out.println(bestScore + "bestScore");
					if ((score < bestScore && color == Piece.BLACK) || (score > bestScore && color == Piece.WHITE)) { // determines the best score, and thus best move
						bestScore = score;
						bestMove = theMove;
					}
				}
			}
		}
		return bestMove;
	}
	
	private int abBlack(Board b, int color, int depth, int alpha, int beta) {
		if (depth <= 0) {
			return eval(b);
		}
		if (DepthFirstSearch.hasWin(Piece.BLACK, b) != null) //if black wins
			return -1000;
		int score, betaTemp = beta;
		for (int y = 0; y < b.board.length; y++) { //traverses board to find all possible moves in child moves
			for (int x = 0; x <b.board[0].length; x++) {
				Move theMove = new Move(x, y);
				if (b.isLegal(theMove, color)) {
					b.makeMove(theMove, color);
					score = -abBlack(b,(color+1)%2, depth-1, -betaTemp, -alpha); //searches open window
					if (score > alpha && score < beta && (y != 0 && x != 0)) {
						score = -abBlack(b,(color+1)%2, depth-1, -beta, -alpha); //researches zero window
					}
					b.clearSpot(x, y);
					alpha = Math.max(alpha, score);
					if (alpha >= beta){ //beta cutoff
						return alpha;
					}
					betaTemp = alpha + 1; //sets new null window
				}
			}
		}
		return (int)(.995 * alpha); // .995 for each successive depth to favor children higher in the tree
	}
	private int abWhite(Board b, int color, int depth, int alpha, int beta) { //same as abBlack but with alpha cutoff
		if (depth <= 0) {
			return eval(b);
		}
		if (DepthFirstSearch.hasWin(Piece.WHITE, b) != null)
			return 1000;
		int score, alphaTemp = alpha;
		for (int y = 0; y < b.board.length; y++) {
			for (int x = 0; x <b.board[0].length; x++) {
				Move theMove = new Move(x, y);
				if (b.isLegal(theMove, color)) {
					b.makeMove(theMove, color);
					score = -abWhite(b,(color+1)%2, depth-1, -beta, -alphaTemp);
					if (score > alpha && score < beta && depth > 0 && (y != 0 && x != 0)) {
						beta = -abWhite(b,(color+1)%2, depth-1, -score, -alpha);
					}
					b.clearSpot(x, y);
					beta = Math.min(beta, score);
					if (beta <= alpha){
						return beta;
					}
					alphaTemp = beta - 1;
				}
			}
		}
		return (int)(.995 * beta);
	}

	/**
	 * Returns a decimal ranging from -1.0 (favoring black) to 1.0 (favoring white).
	 * @param b
	 * @param m
	 * @return
	 */
	public int eval(Board b){
		if (boardMap.containsKey(b))
			return boardMap.get(b);
		if (DepthFirstSearch.hasWin(Piece.BLACK, b) != null)
			return -1000;
		if (DepthFirstSearch.hasWin(Piece.WHITE, b) != null)
			return 1000;
		
		ArrayList<Integer> bList = DepthFirstSearch.getNetworkLengths(b, Piece.BLACK);
		ArrayList<Integer> wList = DepthFirstSearch.getNetworkLengths(b, Piece.WHITE);
		int bScore = 0, score = 0;
		if (bList == null || wList == null)
			return 0;
		for (int x = 0; x < bList.size(); x++) {
			bScore += Math.pow(bList.get(x), 2);
		}
		for (int x = 0; x < wList.size(); x++) {
			score += Math.pow(wList.get(x), 2);
		}
		score = (int)(2000 * ((double)score/(bScore+score))) - 1000;
		//System.out.println("black: " + bScore);
		//System.out.println("score: " +score);
		boardMap.put(b, score);
		return score;
		/*if (Minimax.color == Piece.BLACK) {
			if (bScore > wScore)
				return bScore;
			else
				return -wScore;
		}
		else {
			if (bScore > wScore)
				return -bScore;
			else
				return wScore;
		}*/
	}
}
